package org.hackystat.projectbrowser;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.apache.wicket.Request;
import org.apache.wicket.Session;
import org.apache.wicket.protocol.http.WebSession;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.ambientdevice.server.AmbientServer;
import org.hackystat.ambientdevice.server.jaxb.Configuration;
import org.hackystat.ambientdevice.server.jaxb.TriggerActionPair;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef;
import org.hackystat.telemetry.service.client.TelemetryClient;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.stacktrace.StackTrace;

/**
 * Provides a session instance that holds authentication credentials.
 * 
 * @author Philip Johnson
 * @author Sonwright M. Gomez
 * 
 */
public class ProjectBrowserSession extends WebSession {
  /** Support serialization */
  private static final long serialVersionUID = 1L;
  /** The email used to connect to the SensorBase. */
  private String email = null;
  /** The password for the SensorBase. */
  private String password = null;
  /** The current signinFeedback message to display. */
  private String signinFeedback = "";
  /** The current registerFeedback message to display. */
  private String registerFeedback = "";
  /** If this user has been authenticated against the Sensorbase during this session. */
  private boolean isAuthenticated = false;
  /** The collection of Projects that this user has. */
  private Map<String, Project> projectMap = null;
  /** The collection of Projects that this user has. */
  private List<Project> projectList = null;
  /** The analysis list. */
  public List<String> analysisList = new ArrayList<String>();
  /** The List of project from XML config. file. */
  private List<org.hackystat.ambientdevice.server.jaxb.Project> projectsTypeXML = 
    new ArrayList<org.hackystat.ambientdevice.server.jaxb.Project>();
  private List<TriggerActionPair> pairs = new ArrayList<TriggerActionPair>();
  /** The collection of Projects that this user has from XML file. */
  private Map<String, org.hackystat.ambientdevice.server.jaxb.Project> projectTypeXMLMap = 
    new HashMap<String, org.hackystat.ambientdevice.server.jaxb.Project>();
  private Configuration configuration = new Configuration();

  /**
   * Provide a constructor that initializes WebSession.
   * 
   * @param request The request object.
   */
  public ProjectBrowserSession(Request request) {
    super(request);
  }

  /**
   * Obtain the current session.
   * 
   * @return The current ProjectBrowserSession.
   */
  public static ProjectBrowserSession get() {
    return (ProjectBrowserSession) Session.get();
  }

  /**
   * Returns true if the user has been authenticated in this session.
   * 
   * @return True if the user has supplied a valid email and password for this sensorbase.
   */
  public boolean isAuthenticated() {
    return this.isAuthenticated;
  }

  /**
   * Used by the Signin form to provide the SensorBase authentication credentials to this session.
   * 
   * @param user The user.
   * @param password The password.
   */
  public void setCredentials(String user, String password) {
    this.email = user;
    this.password = password;
  }

  /**
   * Returns the string to be displayed in the SigninFeedback label.
   * 
   * @return A signin feedback string.
   */
  public String getSigninFeedback() {
    return this.signinFeedback;
  }

  /**
   * Allows other components to set the feedback string for the signin form.
   * 
   * @param signinFeedback The message to be displayed.
   */
  public void setSigninFeedback(String signinFeedback) {
    this.signinFeedback = signinFeedback;
  }

  /**
   * Allows other components to set the feedback string for the register form.
   * 
   * @param registerFeedback The message to be displayed.
   */
  public void setRegisterFeedback(String registerFeedback) {
    this.registerFeedback = registerFeedback;
  }

  /**
   * Returns the string to be displayed in the registerFeedback label.
   * 
   * @return A register feedback string.
   */
  public String getRegisterFeedback() {
    return this.registerFeedback;
  }

  /**
   * Returns true if this email/password combination is valid for this sensorbase.
   * 
   * @param email The email.
   * @param password The password.
   * @return True if valid for this sensorbase.
   */
  public boolean signin(String email, String password) {
    try {
      String host = ((ProjectBrowserApplication) getApplication()).getSensorBaseHost();
      SensorBaseClient client = new SensorBaseClient(host, email, password);
      client.authenticate();
      this.email = email;
      this.password = password;
      this.isAuthenticated = true;
      AmbientServer server = new AmbientServer();
      // this.cl = new ConfigurationLoader();

      // For now, we'll have the server always in verbose mode so we can
      // see what's happening when it starts up via UI.
      HackystatLogger.setLoggingLevel(server.getLogger(), "FINER");
      // returns a list of projects from the ambient server if true
      // if false there are no projects associated with this username
      if (server.uiStart(email, password)) {
        projectsTypeXML = server.getProjects();
        configuration = server.getConfiguration();
        // setTriggerActionPairs();
        // User user = new User();
        // user.setEmail(this.email);
        // user.setPassword(this.password);
        // configuration.setUser(user);
        compareAllProjects();
      }
      else {
        projectsTypeXML.clear();
        System.out.println("in signin else statment.");
        compareAllProjects();
      }
      return true;
    }
    catch (Exception e) {
      this.isAuthenticated = false;
      return false;
    }
  }

  /**
   * Returns a SensorBaseClient instance for this user and session.
   * 
   * @return The SensorBaseClient instance.
   */
  public SensorBaseClient getSensorBaseClient() {
    String host = ((ProjectBrowserApplication) getApplication()).getSensorBaseHost();
    return new SensorBaseClient(host, this.email, this.password);
  }

  /**
   * Returns a TelemetryClient instance for this user and session.
   * 
   * @return The TelemetryClient instance.
   */
  public TelemetryClient getTelemetryClient() {
    String host = ((ProjectBrowserApplication) getApplication()).getTelemetryHost();
    return new TelemetryClient(host, this.email, this.password);
  }

  /**
   * Returns a DailyProjectDataClient instance for this user and session.
   * 
   * @return The DailyProjectDataClient instance.
   */
  public DailyProjectDataClient getDailyProjectDataClient() {
    String host = ((ProjectBrowserApplication) getApplication()).getDailyProjectDataHost();
    return new DailyProjectDataClient(host, this.email, this.password);
  }

  /**
   * Gets the user's email associated with this session.
   * 
   * @return The user.
   */
  public String getUserEmail() {
    return this.email;
  }

  /**
   * Returns the list of project names associated with this user.
   * 
   * @return The list of project names.
   */
  public List<String> getProjectNames() {
    List<String> projectNames = new ArrayList<String>();
    projectNames.addAll(getProjects().keySet());
    Collections.sort(projectNames);
    return projectNames;
  }

  /**
   * Return a map of project names to project instances associated with this user. If the map has
   * not yet been built, get it from the SensorBase and cache it.
   * 
   * @return The map of Project instances.
   */
  public Map<String, Project> getProjects() {
    if (this.projectMap == null) {
      this.projectMap = new HashMap<String, Project>();
      try {
        SensorBaseClient sensorBaseClient = ProjectBrowserSession.get().getSensorBaseClient();
        ProjectIndex projectIndex = sensorBaseClient.getProjectIndex(this.email);
        Set<String> duplicatedProjectNames = new TreeSet<String>();
        for (ProjectRef projectRef : projectIndex.getProjectRef()) {
          Project project = sensorBaseClient.getProject(projectRef);
          Project temp = projectMap.put(project.getName(), project);
          if (temp != null) {
            duplicatedProjectNames.add(project.getName());
            projectMap.put(temp.getName() + " - " + temp.getOwner(), temp);
            projectMap.put(project.getName() + " - " + project.getOwner(), project);
          }
        }
        for (String duplicatedProjectName : duplicatedProjectNames) {
          projectMap.remove(duplicatedProjectName);
        }
      }
      catch (SensorBaseClientException e) {
        Logger logger = ((ProjectBrowserApplication) getApplication()).getLogger();
        logger.warning("Error getting projects for " + this.email + StackTrace.toString(e));
      }
    }
    return this.projectMap;
  }

  /**
   * Return the project associated with the given id. Id is usually the project name. In case of
   * projects with the same name, the id will become projectName - projectOwner
   * 
   * @param projectNameId the given id
   * @return the result project, null if not found.
   */
  public Project getProjectByNameId(String projectNameId) {
    return this.projectMap.get(projectNameId);
  }

  /**
   * Returns the list of Projects associated with this user.
   * 
   * @return The list of Projects.
   */
  public List<Project> getProjectList() {
    if (this.projectList == null) {
      projectList = new ArrayList<Project>();
      try {
        SensorBaseClient sensorBaseClient = ProjectBrowserSession.get().getSensorBaseClient();
        ProjectIndex projectIndex = sensorBaseClient.getProjectIndex(this.email);
        for (ProjectRef projectRef : projectIndex.getProjectRef()) {
          projectList.add(sensorBaseClient.getProject(projectRef));
        }
      }
      catch (SensorBaseClientException e) {
        Logger logger = ((ProjectBrowserApplication) getApplication()).getLogger();
        logger.warning("Error getting projects for " + this.email + StackTrace.toString(e));
      }
    }
    return projectList;
  }

  /**
   * Grabs the projects from the DPD and the project from XML. Checks to make sure only the 
   * projects from DPD are in the XML. Deletes project not on DPD. Adds new project from DPD.
   */
  public void compareAllProjects() {
    // List<String> projectTypeXMLNames = new ArrayList<String>();

    // Remove because of findbugs
    // List<String> projectNames = new ArrayList<String>();
    // projectNames = getProjectNames();

    // if there are projectTypeXML projects

    if (projectsTypeXML.isEmpty()) {
      for (String tempProjectName : getProjectNames()) {
        org.hackystat.ambientdevice.server.jaxb.Project newProject = 
          new org.hackystat.ambientdevice.server.jaxb.Project();
        newProject.setProjectName(tempProjectName);
        getProjectTypeXMLMap().put(newProject.getProjectName(), newProject);
      }
    }

    // if there are NO projectTypeXML projects
    else {
      // projectTypeXMLNames = getProjectTypeXMLNames();
      Map<String, org.hackystat.ambientdevice.server.jaxb.Project> XMLMap = getProjectTypeXMLMap();

      // add all the projects from DPD that are not in there already
      for (String tempName : getProjectNames()) {
        if (!XMLMap.containsKey(tempName)) {
          org.hackystat.ambientdevice.server.jaxb.Project newProject = 
            new org.hackystat.ambientdevice.server.jaxb.Project();
          newProject.setProjectName(tempName);
          getProjectTypeXMLMap().put(newProject.getProjectName(), newProject);
        }
      }

      // delete all old projects not in DPD
      for (String tempName : getProjectTypeXMLNames()) {
        if (!getProjects().containsKey(tempName)) {
          getProjectTypeXMLMap().remove(tempName);
        }
      }
    }
  }

  /**
   * Return the projectTypeXML associated with the given id. Id is usually the project name. In case
   * of projects with the same name, the id will become projectName - projectOwner
   * 
   * @param projectNameId the given id
   * @return the result project, null if not found.
   */
  public org.hackystat.ambientdevice.server.jaxb.Project getProjectTypeXMLByNameId(
      String projectNameId) {
    return this.projectTypeXMLMap.get(projectNameId);
  }

  /**
   * Returns the map of the projects associated with this user from the XML config. file.
   * 
   * @return The map of projects.
   */
  private Map<String, org.hackystat.ambientdevice.server.jaxb.Project> getProjectTypeXMLMap() {
    if (projectsTypeXML.isEmpty()) {
      return this.projectTypeXMLMap;
    }

    else {
      for (org.hackystat.ambientdevice.server.jaxb.Project tempProject : projectsTypeXML) {
        String projectName = tempProject.getProjectName();
        projectTypeXMLMap.put(projectName, tempProject);
      }
      return projectTypeXMLMap;
    }
  }

  /**
   * Returns the list of project names associated with this user from the XML config. file.
   * 
   * @return The list of project names.
   */
  private List<String> getProjectTypeXMLNames() {
    List<String> projectTypeXMLNames = new ArrayList<String>();
    if (projectsTypeXML.isEmpty()) {
      for (org.hackystat.ambientdevice.server.jaxb.Project tempProject : projectsTypeXML) {
        projectTypeXMLNames.add(tempProject.getProjectName());
      }
      Collections.sort(projectTypeXMLNames);
      System.out.println(projectTypeXMLNames.size());
      return projectTypeXMLNames;
    }

    else {
      projectTypeXMLNames.clear();
      System.out.println("ELSE" + projectTypeXMLNames.size());
      return projectTypeXMLNames;
    }
  }

  /**
   * Returns the list of Projects associated with this user from the XML config. file.
   * 
   * @return The list of ProjectTypeXMLs.
   */
  public List<org.hackystat.ambientdevice.server.jaxb.Project> getProjectsFromXML() {
    List<org.hackystat.ambientdevice.server.jaxb.Project> allProjects = 
      new ArrayList<org.hackystat.ambientdevice.server.jaxb.Project>();
    allProjects.addAll(getProjectTypeXMLMap().values());
    return allProjects;
  }

  /**
   * Starts the AmbientServer with the email and password provided.
   * 
   * @return boolean true if the server was able to start.
   */
  public boolean startServer() {
    AmbientServer server = new AmbientServer();

    // For now, we'll have the server always in verbose mode so we can
    // see what's happening when it starts up via UI.
    HackystatLogger.setLoggingLevel(server.getLogger(), "FINER");

    if (server.uiStart(email, password)) {
      projectsTypeXML = server.getProjects();

      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Shuts the AmbientServer Down.
   */
  public void stopServer() {
    AmbientServer server = new AmbientServer();
    server.shutdown();
  }

  /**
   * Gets the configurations for this session. 
   * @return The configuration.
   */
  public Configuration getConfiguration() {
    return this.configuration;
  }

  /**
   * Gets the TriggerActionPairList.
   * @return The list of trigger/action pairs. 
   */
  public List<TriggerActionPair> getTriggerActionPairList() {
    return this.pairs;
  }

  /**
   * Sets the TriggerActionPairs
   */
  public void setTriggerActionPairs() {
    for (org.hackystat.ambientdevice.server.jaxb.Project project : projectsTypeXML) {
      this.pairs.addAll(project.getTriggerActionPairs().getTriggerActionPair());
    }
  }

}
